package com.nulldev.util.networking.async2.udp.client;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import com.nulldev.util.networking.async2.executors.ASync2ThreadPool;
import com.nulldev.util.networking.async2.udp.connectors.UDPConnector;

public class Async2UDPClient {
	private final String fName;
	private final String fDescription;
	private final ArrayList<UDPConnector> fConnectors = new ArrayList<UDPConnector>();
	private final ArrayBlockingQueue<Runnable> fThreadPoolQueue = new ArrayBlockingQueue<Runnable>(50000);
	private final ThreadPoolExecutor fMyThreadPool;

	/**
	 * Constructs a new UDP Client.
	 *
	 * @param name            is the name of the client.
	 * @param description     is the description of the client.
	 * @param corePoolSize    is the number of threads to keep in the pool, even if
	 *                        they are idle.
	 * @param maximumPoolSize is the maximum number of threads to allow in the pool.
	 */
	public Async2UDPClient(String name, String description, int corePoolSize, int maximumPoolSize) {
		fName = name;
		fDescription = description;
		fMyThreadPool = new ASync2ThreadPool(corePoolSize, maximumPoolSize, 2, TimeUnit.SECONDS, fThreadPoolQueue);
	}

	/**
	 * Add a new connector to this client.
	 *
	 * @param name           is the name of the connector.
	 * @param readBufferSize is the maximum number of bytes the connector can read
	 *                       at a time.
	 * @param readTimeOut    is the maximum time in milliseconds the connector can
	 *                       use to read incoming data.
	 * @param serverIP       is the server's ip address the connector will use to
	 *                       send data.
	 * @param serverPort     is the server's udp port.
	 * @return the connector.
	 */
	public UDPConnector addConnector(String name, int readBufferSize, int readTimeOut, InetAddress serverIP, int serverPort) {
		UDPConnector connector = new UDPConnector(this, name, readBufferSize, readTimeOut, serverIP, serverPort);
		fConnectors.add(connector);
		return connector;
	}

	/**
	 * Stop and remove a connector.
	 *
	 * @param name is the name of the connector.
	 */
	public void removeConnector(String name) {
		for (int i = 0; i < fConnectors.size(); i++) {
			if (((UDPConnector) fConnectors.get(i)).getName().equals(name)) {
				((UDPConnector) fConnectors.get(i)).stop();
				fConnectors.remove(i);
				break;
			}
		}
	}

	/**
	 * Send data from all connector's to all hosts.
	 *
	 * @param data is the string to be send.
	 */
	public void sendData(String data) throws IOException {
		for (int i = 0; i < fConnectors.size(); i++) {
			((UDPConnector) fConnectors.get(i)).sendData(data);
		}
	}

	/**
	 * Send data from all connector's to all hosts.
	 *
	 * @param bytes  is the byte array to be send.
	 * @param offset is the position in the data buffer at witch to begin sending.
	 * @param length is the number of the bytes to be send.
	 */
	public void sendData(byte[] bytes, int offset, int length) throws IOException {
		for (int i = 0; i < fConnectors.size(); i++) {
			((UDPConnector) fConnectors.get(i)).sendData(bytes, offset, length);
		}
	}

	/**
	 * Start or restart the client.
	 *
	 * @throws java.net.SocketException
	 */
	public void start() throws SocketException, Exception {
		stop();
		try {
			for (int i = 0; i < fConnectors.size(); i++) {
				((UDPConnector) fConnectors.get(i)).start();
			}
		} catch (SocketException ex) {
			throw ex;
		}
	}

	/**
	 * Stop the client.
	 */
	public void stop() {
		try {
			for (int i = 0; i < fConnectors.size(); i++) {
				((UDPConnector) fConnectors.get(i)).stop();
			}
		} catch (Exception ex) {
		}
	}

	/**
	 * Dispose the client. This method stops the client and disposes all active
	 * members of this class. After calling this method you cannot re-start the
	 * client.
	 */
	public void dispose() {
		stop();
		fMyThreadPool.shutdown();
	}

	public void onDataReceive(UDPConnector connector, DatagramPacket packet) {
		// System.out.println("Data received");
		// System.out.println("---" + packet.getAddress() + ":" + packet.getPort());
		// System.out.println("---" + new String(packet.getData(), 0,
		// packet.getLength()));
	}

	/**
	 * Return the name of the client.
	 *
	 * @return the name of the client.
	 */
	public String getName() {
		return fName;
	}

	/**
	 * Return the description of the client.
	 *
	 * @return the description of the client.
	 */
	public String getDescription() {
		return fDescription;
	}

	/**
	 * Return ArrayList with the client's connectors. Each element of the ArrayList
	 * is a UDPConnector class.
	 *
	 * @return ArrayList with the client's connectors.
	 */
	public ArrayList<UDPConnector> getConnectors() {
		return fConnectors;
	}

	/**
	 * Return the client's Thread Pool.
	 *
	 * @return the client's Thread Pool.
	 */
	public ThreadPoolExecutor getMyThreadPool() {
		return fMyThreadPool;
	}

	/**
	 * Returns the total bytes received.
	 *
	 * @return the total bytes received.
	 */
	public long getBytesIn() {
		int result = 0;
		try {
			for (int i = 0; i < fConnectors.size(); i++) {
				result += ((UDPConnector) fConnectors.get(i)).getBytesIn();
			}
		} catch (Exception ex) {
		}
		return result;
	}

	/**
	 * Returns the total bytes send.
	 *
	 * @return the total bytes send.
	 */
	public long getBytesOut() {
		int result = 0;
		try {
			for (int i = 0; i < fConnectors.size(); i++) {
				result += ((UDPConnector) fConnectors.get(i)).getBytesOut();
			}
		} catch (Exception ex) {
		}
		return result;
	}
}
